module futex

#include <pthread.h>

// 空的 lock(), unlock
// with prof: mutex2 115 < mutex3 167ns/op  < mutex1 290ns/op
// no prof: mutex3 17ns/op < mutex2 27ns/op < mutex1 50ns/op



///

[ref_only]
pub struct Mutex {
    mut:
    impl MutexImpl
    impl1 &Mutex1 = 0
    impl2 &Mutex2 = 0
    impl3 &Mutex3 = 0
}

union MutexImpl {
    mut:
    impl1 &Mutex1
    impl2 &Mutex2
    impl3 &Mutex3
}

pub fn newMutex() &Mutex {
    mut mu := &Mutex{}
    mu.impl.impl1 = newMutex1()
    //mu.impl.impl2 = newMutex2()
    //mu.impl.impl3 = newMutex3()
    //mu.impl3 = newMutex3()
    return mu
}

[inline]
pub fn (mu &Mutex) mlock() {
    mu.impl.impl1.mlock()
    //mu.impl.impl2.mlock()
    //mu.impl.impl3.mlock()
    //mu.impl3.mlock()
}

[inline]
pub fn (mu &Mutex) munlock() {
    mu.impl.impl1.munlock()
    //mu.impl.impl2.munlock()
    //mu.impl.impl3.munlock()
    //mu.impl3.munlock()
}


///////////////////
[ref_only]
pub struct Mutex1 {
    mut:
    futval u32
}

pub fn newMutex1() &Mutex1 {
    mut mu := &Mutex1{}
    mu.futval = 0
    return mu
}

const (
    unlocked = u32(0)
    locked = u32(1)
    sleeping = u32(2)
)

// oldval maybe modified
[inline]
fn cmpxchgu32(addr &u32, oldval u32, newval u32) u32 {
    ep := &oldval
    C.atomic_compare_exchange_strong_u32(addr, ep, newval)
    return *ep
}

pub fn (this &Mutex1) mlock() {
    mut ov := u32(0)
    {
        ov = cmpxchgu32(&this.futval, 0, 1)
        // println("$ov, ${this.futval}")
        if ov == 0 {
            // println("rettt")
            return
        }
    }
    for {
        if ov == 2 || cmpxchgu32(&this.futval, 1, 2) != 0 {
            futeximpl2(&this.futval, C.FUTEX_WAIT | C.FUTEX_PRIVATE_FLAG, 2)
        }
        ov = cmpxchgu32(&this.futval, 0, 2)
        if ov != 0 {
            C.syscall(C.__NR_sched_yield)
            continue
        }else{
            break
        }
    }
}

pub fn (this &Mutex1) munlock() {
    if C.atomic_fetch_sub_u32(&this.futval, 1) != 1{
        C.atomic_store_u32(&this.futval, 0)
        futeximpl2(&this.futval, C.FUTEX_WAKE | C.FUTEX_PRIVATE_FLAG, 1)
    }
}

// https://github.com/eliben/code-for-blog/blob/master/2018/futex-basics/mutex-using-futex.cpp

// 115ns/op
[ref_only]
pub struct Mutex2 {
    mut:
    val C.pthread_mutex_t
}

pub fn newMutex2() &Mutex2 {
    mu := &Mutex2{}
    return mu
}
pub fn (mu &Mutex2) mlock() {
    C.pthread_mutex_lock(&mu.val)
}
pub fn (mu &Mutex2) munlock() {
    C.pthread_mutex_unlock(&mu.val)
}


[ref_only]
pub struct Mutex3 {
    mut:
    val C.pthread_spinlock_t
}

pub fn newMutex3() &Mutex3 {
    mu := &Mutex3{}
    C.pthread_spin_init(&mu.val, 0)
    return mu
}

fn C.pthread_spin_init() int
fn C.pthread_spin_lock() int
fn C.pthread_spin_unlock() int

pub fn (mu &Mutex3) mlock() {
    C.pthread_spin_lock(&mu.val)
}
pub fn (mu &Mutex3) munlock() {
    C.pthread_spin_unlock(&mu.val)
}


